package top.kaoshanji.p2ch23b;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import top.kaoshanji.p2ch22b.SimpleInject;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 代码清单-p2ch23b-587页b：在DI容器创建对象的过程中为其设置增强逻辑
 * author kaoshanji
 * ^_^
 * create time 2025/3/3 16:02
 */
public class CGLibContainer {

    // 切面类里的方法列表
    // 将通知类型与切点绑定
    // 一个切点类型对应切面里指定 增强方法类型 有多个
    static Map<Class<?>, Map<InterceptPoint, List<Method>>> interceptMethodsMap = new HashMap<>();

    // 带有 @Aspect 注解的类，这些类都是切面
    // 一般是通过扫描所有的类得到
    static Class<?>[] aspects = new Class<?>[] {ServiceLogAspect.class, ExceptionAspect.class};

    static {
        init();
    }

    // 创建对象...
    public static <T> T getInstance(Class<T> cls) {
        try {
            // 在创建对象的过程中，原始对象被增强了
            // 故事就是从这里开始的...
            T obj = createInstance(cls);

            Field[] fields = cls.getDeclaredFields();
            for (Field field: fields) {
                if (field.isAnnotationPresent(SimpleInject.class)) {
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }

                    Class<?> fieldCls = field.getType();
                    field.set(obj, getInstance(fieldCls));
                }
            }

            return obj;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    // 根据类型创建实例
    // 这是个入口，为目标对象设置增强
    @SuppressWarnings("unchecked")
    private static <T> T createInstance(Class<T> cls) throws InstantiationException, IllegalAccessException {

        // 不需要增强
        if (!interceptMethodsMap.containsKey(cls)) {
            return (T) cls.newInstance();
        }

        // 增强...增强...增强...增强...增强...
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(new AspectInterceptor());

        return (T) enhancer.create();
    }


    // 分析带有 @Aspect 注解的类，并初始化 interceptMethodsMap
    private static void init() {
        for (Class<?> cls: aspects) {
            Aspect aspect = cls.getAnnotation(Aspect.class);
            if (null != aspect) {

                // 找到切面类里的 三类 方法
                Method before = getMethod(cls, "before", new Class<?>[]{Object.class, Method.class, Object[].class});
                Method after = getMethod(cls, "after", new Class<?>[] { Object.class, Method.class, Object[].class, Object.class});
                Method exception = getMethod(cls, "exception", new Class<?>[] { Object.class, Method.class, Object[].class, Throwable.class });

                // 切面类 @Aspect 注解的值
                // 也就是目标类
                Class<?>[] intercepttedArr = aspect.value();

                // 初始化 interceptMethodsMap
                for (Class<?> interceptted: intercepttedArr) {
                    // 增强方法类型 与 切点类型 绑定
                    addInterceptMethod(interceptted, InterceptPoint.BEFORE, before);
                    addInterceptMethod(interceptted, InterceptPoint.AFTER, after);
                    addInterceptMethod(interceptted, InterceptPoint.EXCEPTION, exception);
                }

            }
        }
    }

    // 根据方法名称和参数找到切面类中对应的方法
    private static Method getMethod(Class<?> cls, String name, Class<?>[] paramTypes) {
        try {
            return cls.getMethod(name, paramTypes);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    // 初始化 interceptMethodsMap
    // method 是切面里的 增强方法
    private static void addInterceptMethod(Class<?> cls, InterceptPoint point, Method method) {
        if (null == method) {
            return;
        }

        Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);
        if (null == map) {
            map = new HashMap<>();
            interceptMethodsMap.put(cls, map);
        }

        List<Method> methods = map.get(point);
        if (null == methods) {
            methods = new ArrayList<>();
            map.put(point, methods);
        }

        methods.add(method);

    }

    // 增强的逻辑
    static class AspectInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {

            // 执行切面里的 增强 before 方法
            List<Method> beforeMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.BEFORE);
            for (Method m: beforeMethods) {
                m.invoke(null, new Object[] { object, method, args });
            }

            try {
                // 调用原始方法
                Object result = proxy.invokeSuper(object, args);

                // 执行切面里的 增强 after 方法
                List<Method> afterMethod = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.AFTER);
                for (Method m: afterMethod) {
                    m.invoke(null, new Object[] { object, method, args, result });
                }

                return result;
            } catch (Throwable e) {
                // 执行切面里的 增强 exception 方法
                List<Method> exceptionMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.EXCEPTION);
                for (Method m : exceptionMethods) {
                    m.invoke(null, new Object[] { object, method, args, e });
                }
                throw e;

            }

        }
    }

    // 找到符合切点的方法列表
    // 根据切点在切面里找到对应的 增强方法类型
    static List<Method> getInterceptMethods(Class<?> cls, InterceptPoint point) {
        Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);
        if (null == map) {
            return Collections.emptyList();
        }

        List<Method> methods = map.get(point);
        if (null == methods) {
            return Collections.emptyList();
        }

        return methods;
    }



}
